/*
MIT License
Copyright (c) 2017 GoodieBag
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
 */

package com.goodiebag.pinview;

import ohos.agp.colors.RgbColor;
import ohos.agp.components.*;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.utils.Color;
import ohos.agp.utils.TextAlignment;
import ohos.agp.window.dialog.ToastDialog;
import ohos.app.Context;
import ohos.multimodalinput.event.KeyEvent;

import java.util.ArrayList;
import java.util.List;

/**
 * This class implements a pinview for ohos.
 * It can be used as a widget in ohos to take passwords/OTP/pins etc.
 * It is extended from a LinearLayout, implements TextWatcher, FocusChangeListener and OnKeyListener.
 * Supports drawableItem/selectors as a background for each pin box.
 * A listener is wired up to monitor complete data entry.
 * Can toggle cursor visibility.
 * Supports numpad and text keypad.
 * Flawless focus change to the consecutive pinbox.
 * Date : 11/01/17
 *
 * @author Krishanu
 * @author Pavan
 * @author Koushik
 */
public class Pinview extends DirectionalLayout implements
        Component.ClickedListener, Component.KeyEventListener, Component.FocusChangedListener, TextField.TextObserver {
    /**
     * Attributes
     */
    private int mPinLength = 4;
    private List<TextField> textFields = new ArrayList<>();
    private int mPinWidth = 100;
    private int mTextSize = 50;
    private int mPinHeight = 100;
    private int mSplitWidth = 20;
    private boolean isCursorVisible = false;
    private boolean isDelPressed = false;
    private boolean isToast = false;
    private boolean isPassword = false;
    private String mHint = "";
    private InputType inputType = InputType.TEXT;
    private String mInputType = "text";
    private Component currentFocus = null;
    private int mFlag = 0;
    private int count = 0;
    private int number = 0;
    private boolean isDeleteOk = false;
    private int size;

    /**
     * 构造
     *
     * @param context 上下文
     */
    public Pinview(Context context) {
        this(context, null);
        setOrientation(HORIZONTAL);
    }

    /**
     * 构造
     *
     * @param context 上下文
     * @param attrs attrs
     */
    public Pinview(Context context, AttrSet attrs) {
        this(context, attrs, null);
        setOrientation(HORIZONTAL);
    }

    /**
     * 构造
     *
     * @param context 上下文
     * @param attrs attrs
     * @param defStyleAttr defStyleAttr
     */
    public Pinview(Context context, AttrSet attrs, String defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setOrientation(HORIZONTAL);
        init(context, attrs, defStyleAttr);
    }

    @Override
    public void onClick(Component component) {
        if (MyValue.getTemp() != null && MyValue.getTemp() != component) {
            if (MyValue.getPage() == 4) {
                selectFlag(MyValue.getTemp(), 3);
            } else {
                selectFlag(MyValue.getTemp(), 2);
            }
            MyValue.getTemp().clearFocus();
        }
        MyValue.getPinviews().clear();
        MyValue.getFields().clear();
        for (int index = 0; index < ((DirectionalLayout) getComponentParent()).getChildCount(); index++) {
            if (((DirectionalLayout) getComponentParent()).getComponentAt(index) instanceof Pinview) {
                MyValue.getPinviews().add((Pinview) ((DirectionalLayout) getComponentParent()).getComponentAt(index));
            }
        }

        for (int pinIndex = 0; pinIndex < MyValue.getPinviews().size(); pinIndex++) {
            Pinview pinView = MyValue.getPinviews().get(pinIndex);
            for (int index = 0; index < pinView.getChildCount(); index++) {
                MyValue.getFields().add((TextField) pinView.getComponentAt(index));
            }
        }
        if (MyValue.getFields().size() > 0) {
            for (TextField field : MyValue.getFields()) {
                size = MyValue.getSize();
                size++;
                MyValue.setSize(size);
                if (field == component) {
                    break;
                }
            }
        }
        if (MyValue.getSize() >= 0 && MyValue.getSize() <= mPinLength - 1) {
            MyValue.setPage(0);
        } else if (MyValue.getSize() > mPinLength - 1 && MyValue.getSize() <= mPinLength * 2 - 1) {
            MyValue.setPage(1);
        } else if (MyValue.getSize() > mPinLength * 2 - 1 && MyValue.getSize() <= mPinLength * 3 - 1) {
            MyValue.setPage(2);
        } else if (MyValue.getSize() > mPinLength * 3 - 1 && MyValue.getSize() <= mPinLength * 4 - 1) {
            MyValue.setPage(3);
        }
        MyValue.setSize(0);
        if (!isCursorVisible) {
            for (int i = 0; i < Integer.parseInt(component.getTag().toString()); i++) {
                TextField textField = textFields.get(i);
                String text = textField.getText();
                if (text == null || text.length() == 0) {
                    component.setEnabled(false);
                    component.setFocusable(FOCUS_DISABLE);
                    count++;
                }
            }
            if (count == 0) {
                if (((TextField) component).getText().length() == 0) {
                    component.setFocusable(FOCUS_ENABLE);
                    component.setEnabled(true);
                    component.requestFocus();
                    selectFlag((TextField) component, 1);
                } else {
                    for (int i = 0; i < mPinLength; i++) {
                        TextField textField = textFields.get(i);
                        if (textField.length() == 0) {
                            number++;
                            textField.setFocusable(FOCUS_ENABLE);
                            textField.setEnabled(true);
                            textField.requestFocus();
                            if (mFlag != 3) {
                                setPinBackgroundRes(textField, 1);
                            } else {
                                setPinBackgroundRes(textField, mFlag);
                            }
                            component.clearFocus();
                            return;
                        }
                    }
                    if (number == 0) {
                        TextField textField = textFields.get(mPinLength - 1);
                        if (mFlag != 3) {
                            setPinBackgroundRes(textField, 1);
                        } else {
                            setPinBackgroundRes(textField, mFlag);
                        }
                    } else {
                        number = 0;
                    }
                }
            }
        } else {
            component.setFocusable(FOCUS_ENABLE);
            component.setEnabled(true);
            component.requestFocus();
        }
        if (count != 0) {
            count = 0;
        }
    }

    private enum InputType {
        TEXT, NUMBER, PASSWORD
    }

    private void init(Context context, AttrSet attrs, String defStyleAttr) {
        initAttributes(context, attrs, defStyleAttr);
        createTextFields();
    }

    /**
     * Requsets focus on current pin view and opens keyboard if forceKeyboard is enabled.
     *
     * @return the current focused pin view. It can be used to open softkeyboard manually.
     */
    public Component requestPinEntryFocus() {
        int currentTag = Math.max(0, getIndexOfCurrentFocus());
        TextField textField = textFields.get(currentTag);
        if (textField != null) {
            textField.requestFocus();
        }
        return textField;
    }

    /**
     * Creates textfields and adds it to the pinview based on the pinLength specified.
     */

    private void createTextFields() {
        for (int i = 0; i < mPinLength; i++) {
            TextField textField = new TextField(getContext());
            textField.setTextSize(mTextSize);
            textField.setHint(mHint);
            generateOneTextField(textField, "" + i);
            addComponent(textField);
            textFields.add(i, textField);
        }
    }

    private void initAttributes(Context context, AttrSet attrs, String defStyleAttr) {
        if (attrs != null) {
            mInputType = TypedAttrUtils.getString(attrs, "inputType", mInputType);
            mPinLength = TypedAttrUtils.getInteger(attrs, "pinLength", mPinLength);
            mHint = TypedAttrUtils.getString(attrs, "hint", mHint);
            mFlag = TypedAttrUtils.getInteger(attrs, "flag", mFlag);
            mPinHeight = TypedAttrUtils.getDimensionPixelSize(attrs, "pinHeight", mPinHeight);
            mPinWidth = TypedAttrUtils.getDimensionPixelSize(attrs, "pinWidth", mPinWidth);
            mSplitWidth = TypedAttrUtils.getDimensionPixelSize(attrs, "splitWidth", mSplitWidth);
            mTextSize = TypedAttrUtils.getDimensionPixelSize(attrs, "textSize", mTextSize);
            isCursorVisible = TypedAttrUtils.getBoolean(attrs, "cursorVisible", isCursorVisible);
            isToast = TypedAttrUtils.getBoolean(attrs, "isToast", isToast);
            isPassword = TypedAttrUtils.getBoolean(attrs, "password", isPassword);
        }
    }

    /**
     * Takes care of styling the textfield passed in the param.
     * tag is the index of the textfield.
     *
     * @param field field
     * @param tag tag
     */
    private void generateOneTextField(TextField field, String tag) {
        if (!isCursorVisible) {
            if (Integer.parseInt(tag) == 0) {
                field.setClickable(true);
            } else {
                field.setClickable(false);
                field.setEnabled(false);
                field.setFocusable(FOCUS_DISABLE);
            }
        }
        field.setHeight(mPinHeight);
        field.setWidth(mPinWidth);
        field.setTextCursorVisible(isCursorVisible);
        if (isCursorVisible) {
            setCursorColor(field);
        }
        field.setMarginRight(mSplitWidth);
        field.setTextAlignment(TextAlignment.CENTER);
        field.setTag(tag);

        field.setTextInputType(getKeyboardInputType());
        setPinBackgroundRes(field, mFlag);

        field.setClickedListener(this);
        field.addTextObserver(this);
        field.setKeyEventListener(this);
        field.setFocusChangedListener(this);
    }

    private void setCursorColor(TextField field) {
        ShapeElement shapeElement = new ShapeElement();
        shapeElement.setShape(ShapeElement.LINE);
        shapeElement.setRgbColors(new RgbColor[]{RgbColor.fromArgbInt(
                Color.rgb(Constant.COLOR_156, Constant.COLOR_39, Constant.COLOR_176)),
                RgbColor.fromArgbInt(Color.rgb(33, 150, 243))});
        shapeElement.setGradientOrientation(ShapeElement.Orientation.TOP_TO_BOTTOM);
        shapeElement.setStroke(mTextSize, RgbColor.fromArgbInt(Color.rgb(Constant.COLOR_255, 0, 0)));
        field.setCursorElement(shapeElement);
    }

    /**
     * 设置光标颜色
     *
     * @param startColor 起始
     * @param endColor 结束
     */
    public void setCursorColor(Color startColor, Color endColor) {
        if (textFields == null || textFields.isEmpty()) {
            return;
        }
        for (TextField edt : textFields) {
            setCursorColor(edt, startColor, endColor);
        }
    }

    private void setCursorColor(TextField field, Color startColor, Color endColor) {
        ShapeElement shapeElement = new ShapeElement();
        shapeElement.setShape(ShapeElement.LINE);
        shapeElement.setRgbColors(new RgbColor[]{RgbColor.fromArgbInt(startColor.getValue()),
                RgbColor.fromArgbInt(endColor.getValue())});
        shapeElement.setGradientOrientation(ShapeElement.Orientation.TOP_TO_BOTTOM);
        shapeElement.setStroke(mTextSize, RgbColor.fromArgbInt(Color.rgb(Constant.COLOR_255, 0, 0)));
        field.setCursorElement(shapeElement);
    }

    /**
     * getPinBackground
     *
     * @param textField textField
     * @return ShapeElement
     */
    public ShapeElement getPinBackground(TextField textField) {
        return (ShapeElement) textField.getBackgroundElement();
    }

    public void setFlag(int flag) {
        this.mFlag = flag;
    }

    public int getFlag() {
        return mFlag;
    }

    /**
     * setPinBackgroundRes
     *
     * @param field field
     * @param flag flag
     */
    public void setPinBackgroundRes(TextField field, int flag) {
        ShapeElement shapeElement = new ShapeElement();
        shapeElement.setShape(ShapeElement.RECTANGLE);
        if (flag == 1) {
            shapeElement.setStroke(Constant.NUM_2,
                    RgbColor.fromArgbInt(Color.rgb(Constant.COLOR_254, Constant.COLOR_67, Constant.COLOR_101)));
            shapeElement.setRgbColor(RgbColor.fromArgbInt(Color.rgb(Constant.COLOR_255, Constant.COLOR_255, 0)));
        } else if (flag == 0) {
            shapeElement.setRgbColor(RgbColor.fromArgbInt(Color.rgb(Constant.COLOR_151,
                    Constant.COLOR_151, Constant.COLOR_151)));
        } else if (flag == Constant.NUM_2) {
            shapeElement.setStroke(Constant.NUM_2,
                    RgbColor.fromArgbInt(Color.rgb(Constant.COLOR_254, Constant.COLOR_67, Constant.COLOR_101)));
            shapeElement.setRgbColor(RgbColor.fromArgbInt(Color.rgb(Constant.COLOR_255,
                    Constant.COLOR_255, Constant.COLOR_255)));
        } else {
            shapeElement.setStroke(Constant.NUM_2, RgbColor.fromArgbInt(Color.rgb(0, 0, 0)));
            shapeElement.setRgbColor(RgbColor.fromArgbInt(Color.rgb(Constant.COLOR_255,
                    Constant.COLOR_255, Constant.COLOR_255)));
        }
        shapeElement.setCornerRadius(Constant.NUM_10);
        field.setBackground(shapeElement);
        field.setMaxTextLines(1);
    }

    private static boolean isNumeric(String str) {
        String reg = "^[0-9]+(.[0-9]+)?$";
        return str.matches(reg);
    }

    private int getKeyboardInputType() {
        int it;
        switch (mInputType) {
            case "number":
            case "numberPassword":
                inputType = InputType.NUMBER;
                break;
            case "text":
                inputType = InputType.TEXT;
                break;
            default:
                inputType = InputType.PASSWORD;
        }
        switch (inputType) {
            case NUMBER:
                it = InputAttribute.PATTERN_NUMBER;
                break;
            case TEXT:
                it = InputAttribute.PATTERN_TEXT;
                break;
            default:
                it = InputAttribute.PATTERN_PASSWORD;
        }
        return it;
    }

    /**
     * Returns the value of the Pinview
     *
     * @return String
     */
    public String getValue() {
        StringBuilder sb = new StringBuilder();
        for (TextField et : textFields) {
            sb.append(et.getText());
        }
        return sb.toString();
    }

    @Override
    public void onTextUpdated(String s, int i, int i1, int i2) {
        int indexOfCurrentFocus = getIndexOfCurrentFocus();

        if ("numberPassword".equals(mInputType)) {
            textFields.get(indexOfCurrentFocus).setTextInputType(InputAttribute.PATTERN_PASSWORD);
        }

        if ("numberPassword".equals(mInputType) || "number".equals(mInputType)) {
            if (!isNumeric(s)) {
                return;
            }
        }

        if (s.length() == 1) {
            if (indexOfCurrentFocus == mPinLength - 1) {
                String value = getValue();
                if (isToast) {
                    new ToastDialog(getContext())
                            .setText(value)
                            .setSize(Constant.NUM_300, Constant.NUM_100)
                            .setDuration(Constant.NUM_3000)
                            .show();
                    isToast = false;
                }
            }
            if (indexOfCurrentFocus + 1 <= mPinLength - 1) {
                TextField textField1 = textFields.get(indexOfCurrentFocus + 1);
                textField1.setFocusable(FOCUS_ENABLE);
                textField1.setEnabled(true);
                textField1.requestFocus();
                if (isCursorVisible) {
                    setCursorColor(textFields.get(indexOfCurrentFocus + 1));
                }
                selectFlag(textField1, 1);
            }
            TextField textField = textFields.get(indexOfCurrentFocus);
            selectFlag(textField, Constant.NUM_2);
            if (textField != textFields.get(mPinLength - 1)) {
                textField.clearFocus();
            } else {
                selectFlag(textField, 1);
            }
        } else if (s.length() > 1) {
            String substring = s.substring(0, 1);
            textFields.get(indexOfCurrentFocus).setText(substring);
        }
    }

    /**
     * selectFlag
     *
     * @param textField textField
     * @param i3 位置
     */
    public void selectFlag(TextField textField, int i3) {
        if (mFlag != Constant.NUM_3) {
            setPinBackgroundRes(textField, i3);
        } else {
            setPinBackgroundRes(textField, Constant.NUM_3);
        }
    }

    @Override
    public boolean onKeyEvent(Component component, KeyEvent keyEvent) {
        if (keyEvent.getKeyCode() == KeyEvent.KEY_DEL && keyEvent.isKeyDown()) {
            int currentTag = getIndexOfCurrentFocus();
            if (currentTag > 0) {
                isDelPressed = true;
                if (textFields.get(currentTag).length() == 0 && isDeleteOk) {
                    textFields.get(currentTag - 1).setEnabled(true);
                    textFields.get(currentTag - 1).setFocusable(FOCUS_ENABLE);
                    textFields.get(currentTag - 1).requestFocus();
                    textFields.get(currentTag - 1).setHint(mHint);
                    selectFlag(textFields.get(currentTag - 1), 1);
                    selectFlag(textFields.get(currentTag), mFlag);
                    if (isCursorVisible) {
                        ShapeElement shapeElement = new ShapeElement();
                        shapeElement.setShape(ShapeElement.LINE);
                        shapeElement.setRgbColor(RgbColor.fromArgbInt(Color.rgb(Constant.COLOR_255,
                                Constant.COLOR_255, Constant.COLOR_255)));
                        shapeElement.setStroke(mTextSize, RgbColor.fromArgbInt(Color.rgb(Constant.COLOR_255,
                                Constant.COLOR_255, Constant.COLOR_255)));
                        textFields.get(currentTag).setCursorElement(shapeElement);
                    }
                } else {
                    selectFlag(textFields.get(currentTag), 1);
                    isDeleteOk = true;
                }
            } else if (currentTag == 0) {
                if (textFields.get(currentTag).getText().length() > 0) {
                    if (mFlag == Constant.NUM_3) {
                        setPinBackgroundRes(textFields.get(currentTag), Constant.NUM_3);
                    } else {
                        setPinBackgroundRes(textFields.get(currentTag), 1);
                    }
                }
            }
        } else if (keyEvent.isKeyDown() && keyEvent.getKeyCode() == KeyEvent.KEY_ENTER) {
            if (MyValue.getPage() == 0) {
                enter(1);
                MyValue.setPage(1);
            } else if (MyValue.getPage() == 1) {
                enter(2);
                MyValue.setPage(2);
            } else if (MyValue.getPage() == 2) {
                enter(3);
                MyValue.setPage(3);
            } else if (MyValue.getPage() == 3) {
                enter(4);
                MyValue.setPage(4);
            }
            return true;
        }
        return false;
    }

    private void enter(int page) {
        Pinview pinview = MyValue.getPinviews().get(page);
        int childCount = pinview.getChildCount();
        for (int index = 0; index < childCount; index++) {
            TextField field = (TextField) pinview.getComponentAt(index);
            if (field.length() == 0) {
                int focus = getIndexOfCurrentFocus();
                TextField textField = textFields.get(focus);
                if (focus == 0) {
                    if (page == 4) {
                        selectFlag(textField, 3);
                    } else {
                        selectFlag(textField, 2);
                    }
                } else {
                    if (page == 4) {
                        selectFlag(textField, 3);
                    } else {
                        selectFlag(textField, 0);
                    }
                }
                MyValue.setTemp(field);
                field.setFocusable(FOCUS_ENABLE);
                field.setEnabled(true);
                field.requestFocus();
                textField.clearFocus();
                if (page == 4) {
                    selectFlag(field, 3);
                } else {
                    selectFlag(field, 1);
                }
                break;
            }
        }
    }

    @Override
    public void onFocusChange(Component component, boolean isFocused) {
        if (isFocused && !isCursorVisible) {
            if (isDelPressed) {
                currentFocus = component;
                isDelPressed = false;
                return;
            }
            for (final TextField field : textFields) {
                if (field.length() == 0) {
                    if (field != component) {
                        field.setFocusable(FOCUS_ENABLE);
                        field.setEnabled(true);
                        field.requestFocus();
                    } else {
                        currentFocus = component;
                    }
                    return;
                }
            }
            if (textFields.get(mPinLength - 1) != component) {
                textFields.get(mPinLength - 1).setFocusable(FOCUS_ENABLE);
                textFields.get(mPinLength - 1).setEnabled(true);
                textFields.get(mPinLength - 1).requestFocus();
            } else {
                currentFocus = component;
            }
        } else if (isFocused) {
            currentFocus = component;
        } else {
            component.clearFocus();
            selectFlag((TextField) component, Constant.NUM_2);
        }
    }

    private int getIndexOfCurrentFocus() {
        return textFields.indexOf(currentFocus);
    }

    public boolean isPassword() {
        return isPassword;
    }

    public void setPassword(boolean isPassword) {
        this.isPassword = isPassword;
    }

    /**
     * Clears the values in the Pinview
     */
    public void clearValue() {
        setValue("");
    }

    /**
     * Sets the value of the Pinview
     *
     * @param value 字符
     */
    public void setValue(String value) {
        for (int index = 0; index < textFields.size(); index++) {
            if (value.length() > index) {
                currentFocus = textFields.get(index);
                textFields.get(index).setText(String.valueOf(value.charAt(index)));
            } else {
                textFields.get(index).setText("");
            }
        }
    }

    public int getSplitWidth() {
        return mSplitWidth;
    }

    /**
     * 设置间隔
     *
     * @param splitWidth 间隔
     */
    public void setSplitWidth(int splitWidth) {
        this.mSplitWidth = splitWidth;

        for (TextField field : textFields) {
            field.setMarginRight(mSplitWidth);
            field.setHeight(mPinHeight);
            field.setWidth(mPinWidth);
        }
    }

    public int getPinHeight() {
        return mPinHeight;
    }

    /**
     * 设置高度
     *
     * @param pinHeight 高度
     */
    public void setPinHeight(int pinHeight) {
        this.mPinHeight = pinHeight;
        for (TextField textField : textFields) {
            textField.setHeight(mPinHeight);
            textField.setWidth(mPinWidth);
        }
    }

    public int getPinWidth() {
        return mPinWidth;
    }

    /**
     * 设置宽度
     *
     * @param pinWidth 宽度
     */
    public void setPinWidth(int pinWidth) {
        this.mPinWidth = pinWidth;
        for (TextField field : textFields) {
            field.setHeight(mPinHeight);
            field.setWidth(mPinWidth);
        }
    }

    public int getPinLength() {
        return mPinLength;
    }

    public void setPinLength(int pinLength) {
        this.mPinLength = pinLength;
    }

    public String getHint() {
        return mHint;
    }

    public void setHint(String hint) {
        this.mHint = hint;
    }

    public InputType getInputType() {
        return inputType;
    }

    public void setInputType(InputType inputType) {
        this.inputType = inputType;
    }

    /**
     * 是否显示光标
     *
     * @param isStatus 标记
     */
    public void showCursor(boolean isStatus) {
        this.isCursorVisible = isStatus;
    }

    public void setTextSize(int textSize) {
        mTextSize = textSize;
    }

    /**
     * 设置文本颜色
     *
     * @param color color
     */
    public void setTextColor(Color color) {
        if (textFields == null || textFields.isEmpty()) {
            return;
        }
        for (TextField edt : textFields) {
            edt.setTextColor(color);
        }
    }

    /**
     * 设置光标样式
     *
     * @param shape 样式
     */
    public void setCursorShape(int shape) {
        if (textFields == null || textFields.isEmpty()) {
            return;
        }
        for (TextField edt : textFields) {
            ShapeElement shapeElement = new ShapeElement();
            shapeElement.setShape(ShapeElement.LINE);
            edt.setBackground(shapeElement);
        }
    }
}
